<?php
/**
 * Dispatcher
 *
 * 구동시킬 컨트롤러의 검색, 처리의 역할을 합니다.
 *
 * @package boot
 * @author  Platform Team
 */

class Dispatcher
{
    /**
     * 인스턴스
     * @var Dispatcher
     */
    private static $instance = null;

    /**
     * 컨트롤러 인스턴스
     */
    private $aCont = array();

    /**
     * 컨트롤러 명
     */
    private $sController;

    /**
     * 메소드 명
     */
    private $sMethod;

    /**
     * 메소드 파라미터
     */
    private $aMethodParam;

    /**
     * 컨트롤러 결과물
     */
    private $sOutput;

    /**
     * View 루트 디렉토리
     */
    private $sViewRoot;

    /**
     * Resource 루트 디렉토리
     */
    private $sResRoot;

    /**
     * 모델 객체
     *
     * @var Model
     */
    private $model;

    /**
     * 뷰 객체
     *
     * @var View
     */
    private $view;

    /**
     * 언어팩 객체
     *
     * @deprecated
     * @var Language
     */
    private $lang;

    /**
     * 옵티마이져 객체
     *
     * @var Optimizer
     */
    private $optimizer;

    /**
     * 구동 여부
     */
    private $isProgress;

    /**
     * 생성자
     */
    private function __construct() {}

    /**
     * singleton
     *
     * @return Dispatcher
     */
    public static function getInstance()
    {
        if (self::$instance === null) self::$instance = new self;

        return self::$instance;
    }

    /**
     * 인스턴스 반환
     *
     * @return Dispatcher
     */
    public static function instance()
    {
        return self::$instance;
    }

    /**
     * 인스턴스 파괴
     */
    public static function instancedel()
    {
        self::$instance = null;
    }

    /**
     * 초기화
     *
     * @param string $sController  (컨트롤러명)
     * @param string $sMethod      (메소드명)
     * @param array $aMethodParam (메소드 파라미터)
     * @return boolean
     */
    public function initialize($sController, $sMethod, $aMethodParam=null)
    {
        if ($this->_isSubObjectLoad() === false) {
            throw new Exception('Dispatcher initialize failed', E_ERROR);
        }

        $this->sController  = $sController;
        $this->sMethod      = $sMethod;
        $this->aMethodParam = $aMethodParam;
        $this->sOutput      = '';
        $this->sViewRoot    = FW_DIR_VIEW;
        $this->sResRoot     = FW_DIR_RESOURCE;
        $this->isProgress   = true;

        return true;
    }

    /**
     * MODEL 세팅
     *
     * @param $obj
     */
    public function initModel($obj)
    {
        if($this->isProgress === true) return;

        $this->model = $obj;
    }

    /**
     * VIEW 세팅
     *
     * @param $obj
     */
    public function initView($obj)
    {
        if($this->isProgress === true) return;

        $this->view = $obj;
    }

    /**
     * LANGUAGE 세팅
     *
     * @deprecated
     * @param $obj
     */
    public function initLanguage($obj)
    {
        if($this->isProgress === true) return;

        $this->lang = $obj;
    }

    /**
     * OPTIMIZER 세팅
     *
     * @param $obj
     */
    public function initOptimizer($obj)
    {
        if($this->isProgress === true) return;

        $this->optimizer = $obj;
    }

    /**
     * 실행된 컨트롤러 객체 반환
     *
     * @return object
     */
    public function getController()
    {
        return is_object($this->aCont[$this->sController]) ? $this->aCont[$this->sController] : false;
    }

    /**
     * 컨트롤러 결과 데이터 반환
     *
     * @return string
     */
    public function getOutput()
    {
        return $this->sOutput;
    }

    /**
     * 컨트롤러 결과 데이터 오버라이딩
     *
     * @param string $sOutput
     */
    public function overrideOutput($sOutput)
    {
        $this->sOutput = $sOutput;
    }

    /**
     * View 루트 디렉토리 반환 (최종 Display 에서 활용)
     *
     * @return string
     */
    public function getViewRoot()
    {
        return $this->sViewRoot;
    }

    /**
     * View 루트 디렉토리 반환 (최종 Display 에서 활용)
     *
     * @param string $sDir
     */
    public function overrideViewRoot($sDir)
    {
        if (is_string($sDir) === false) return;

        $this->sViewRoot = $sDir;
    }

    /**
     * Resource 루트 디렉토리 반환 (최종 Display 에서 활용)
     *
     * @return string
     */
    public function getResRoot()
    {
        return $this->sResRoot;
    }

    /**
     * Resource 루트 디렉토리 반환 (최종 Display 에서 활용)
     *
     * @param string $sDir
     */
    public function overrideResRoot($sDir)
    {
        if (is_string($sDir) === false) return;

        $this->sResRoot = $sDir;
    }

    /**
     * EXECUTE - 컨트롤러 호출
     */
    public function exec()
    {
        $sCont = $this->sController;
        $sMethod = $this->sMethod;

        if (class_exists($sCont) === false) {
            Exceptioner::run(FW_EXCEPTION_ROUTER, 'Controller class is not found - ' . $sCont, Router::ERROR_NOT_FOUND_MAP);
            throw new Exception('Controller class is not found - ' . $sCont, E_ERROR);
        }

        ob_start();

        if (empty($this->aCont[$sCont]) === true) {
            $this->aCont[$sCont] = new $sCont($this->model, $this->view, $this->lang, $this->optimizer);
        }

        $Controller = $this->aCont[$sCont];

        $Controller->afterInit();

        if (is_callable(array($Controller, $sMethod)) === true) {

            $aMethodParam = !empty($this->aMethodParam) ? (array)$this->aMethodParam : array();
            call_user_func_array(array($Controller, $sMethod), $aMethodParam);

        } else {

            ob_end_clean();
            Exceptioner::run(FW_EXCEPTION_ROUTER, 'Controller Method is not found - ' . $sMethod, Router::ERROR_NOT_FOUND_MAP);
            throw new Exception('Controller Method is not found - ' . $sMethod, E_ERROR);

        }

        $Controller->afterRun();

        $this->sOutput = ob_get_contents();

        ob_end_clean();

        return true;
    }

    /**
     * EXECUTE - 컨트롤러 결과 출력
     */
    public function display()
    {
        $Optimizer = $this->optimizer;
        $Controller = $this->aCont[$this->sController];

        $sContentType = $Controller->getContentType();
        $sEncodingType = $Controller->getEncodingType();

        if (!$sEncodingType) {
            $sEncodingType = FW_CHARSET;
        }

        $sOutput = $this->sOutput;

        $this->_header($sContentType, $sEncodingType);

        $sTemplate = $Controller->getTemplate();

        if ($sTemplate === null) {
            echo $sOutput;
            return;
        }

        $sRoot = $this->sViewRoot . DS . 'template';

        $aCustom = $Controller->getCustom();
        $aMeta = $Controller->getMetaContent();
        $sTitle = $Controller->getTitle();
        $sJsTag = $Optimizer->makeOptimizerJsTag();
        $sCssTag = $Optimizer->makeOptimizerCssTag();

        $this->view->display($sRoot, $this->sResRoot, $sTemplate, array(
            'title_in_template' => $sTitle,
            'meta_contents_in_template' => $aMeta,
            'js_in_template' => $sJsTag,
            'css_in_template' => $sCssTag,
            'contents_in_template' => $sOutput,
            'custom_in_template' => $aCustom
        ));
    }

    /**
     * 필요한 서브 객체들이 로드 되었는지 확인
     *
     * @return boolean
     */
    private function _isSubObjectLoad()
    {
        if (!$this->model ||
            !$this->view ||
            !$this->lang ||
            !$this->optimizer) {
            return false;
        }

        return true;
    }

    /**
     * 헤더 지정
     *
     * @param string $sType
     * @param string $sEncoding
     */
    private function _header($sType, $sEncoding)
    {
        $sMime = getMime($sType);
        $sMime = $sMime ? $sMime : 'text/html';

        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
        header('Cache-Control: no-store, no-cache, must-revalidate');
        header('Cache-Control: post-check=0, pre-check=0', false);
        header('Pragma: no-cache');
        header('Content-type: ' . $sMime . '; charset=' . $sEncoding);
    }
}
